<?php
/**
 * 框架，我们认为他是一种稳定的结构，和业务无关的一系列约定和机制。
 * 这个文件实现了系统组成元素的定义，及对系统的扩展性保留了足够的空间
 * 
 * 组成元素定义:框架提供了Api、Mod、Open、Face、Script、Lib这几种元素。
 * 		Api提供了系统内部相互调用的机制
 * 		Mod提供了某个完整模块的嵌入
 * 		Open提供了webservice服务
 * 		Face提供界面功能
 * 		Script提供后台脚本运行机制
 * 		Lib和其他元素相比要小一些，在某些情况下我们希望把某一些相同的业务归置到一起，业务功能足够细致，方便分工和维护。那么可以写为一个Lib，供插件内部调用。插件外部无法调用
 * 
 * 扩展性:
 * 		框架自身并不实现任何业务功能，他的职责在于为业务功能提供生存环境。
 * 		为了更多的满足各种不同的需求。框架本身提供了一个基于插件式的机制。
 * 		同时还提供了ser巨大的可改造空间。对于比较高级的开发者，可通过对内核的扩展来满足自己的业务。
 * 		但是并不很推荐你这么做。因为我们认为现有结构已经能满足你99%的需求了。
 * 		如果还无法满足有可能你对框架的理解有偏差，或这个框架可能并不适合您的项目。如果存在这个情况欢迎给我们反馈。
 * 
 * 
 * @author 991<ruiyanlin@sohu-inc.com>
 * 
 * @version 0.01
 * 
 ***********************************************/








/**
 * 框架最低层的基类，所有的类都继承自这个类。主要为框架的可扩展性预留空间。
 * 
 * 如果您发现需要给所有的插件的各个元素加上某一个属性或者方法时，这个类可以把你从崩溃的边沿拉回来。
 * 当然一般情况是不需要的。如果您的某一些需求需要对这个类进行改动。那么请和我们分享您的案例
 * 
 */
class Base {
	
	public function __construct() {

	}
	
	/**
	 * 这个函数对插件内部的保护和友善提示。在您访问一个不存在的方法时。将会自动调用这个函数，这个函数将给你一个准确的提示。
	 * 如果您开启了错误log，那么您能很快的通过日志得到一些隐蔽的bug
	 * 
	 * @param string 	$fun 		方法名
	 * @param array 	$param 		函数的参数，这里将被忽略
	 * 
	 */
	public function __call($fun,$param) {
		throw new CException("找不到调用方法{$fun}");
	}

	
	/**
	 * 检查参数
	 * 每个插件需要对自己负责，通过用户或者接口返回的数据都是不可靠的。所以在这里实现了对参数的处理。
	 * 这个参数多虑后则可以认为是安全的
	 * 
	 * @example $param = $this->param('id:uint:ignore,title:char');
	 * 
	 * @param unknown_type $rule	eg:  id:int:ignore,title:char[:error]
	 * @param unknown_type $data
	 */
	public function param($rule,$data=array()){
		if(!empty($data) && !is_array($data)){
			throw new PException('param:必须为数组', 999);
		}
		//linruiyan@2012-10-30 添加规则换行的替换。让参数能格式化，看着舒服点
		$rule = str_replace(array("\n","\r","\t"), '', $rule);
		
		
//		if(empty($data) && !empty($_POST)){
//			$data = $_POST;
//		}elseif(empty($data)){
//			$data = $_GET;
//		}
		//合并$_POST和$_GET数据
		if(empty($data)){
			$data = array_merge_recursive($_POST,$_GET);
		}
		$items = explode(',',$rule);

		if(empty($items)){
			throw new PException('param:第二参数错误', 999);
		}
		
		
		$rs = array();
		foreach($items as $key=>$val){
			$item = explode(':',$val);
			//默认以错误处理
			$item[2] = isset($item[2])?$item[2]:'error';
			
			
			
			if('keep'==$item[2]){
				$rs[$item[0]] = $data[$item[0]];
				continue;
			}
			
			//如果忽略，且变量不存在则，跳过
			if(!isset($data[$item[0]]) && 'ignore'==$item[2]){
				continue;
			}
			if(isset($data[$item[0]]) && 'empty'==$item[2] && strlen($data[$item[0]])==0){
				throw new PException("{$item[0]}:不允许为空");
			}
			//如果忽略，且变量不存在则，跳过
			if(!isset($data[$item[0]]) && preg_match('/^default\[(.*?)\]/',$item[2],$default)){
				$data[$item[0]] = $default[1];
			}			

			if(!isset($data[$item[0]]) && !in_array($item[1], array('page'))){
				throw new PException("{$item[0]}:参数错误");
			}			
			//++更好的思路是对字符串复杂化，根据数据结构那样进行堆栈处理，解析出参数
			//id:int,type:enum[1,2,3,4,5,6]:default[2],range\20,400/,size/1,9\,height<90,width>80
			switch($item[1]){
				case 'src':$rs[$item[0]] = $data[$item[0]];break;
				case 'int':$rs[$item[0]] = intval($data[$item[0]]);break;
				case 'page':
						if(!isset($data[$item[0]]))$data[$item[0]] = 1;
						$rs[$item[0]] = intval($data[$item[0]]);
						if($rs[$item[0]]<1)$rs[$item[0]]=1;
					break;
				case 'char':
					$rs[$item[0]] = get_magic_quotes_gpc()?trim($data[$item[0]]):addslashes(trim($data[$item[0]]));
					break;
				case 'uint':
						$rs[$item[0]] = intval($data[$item[0]]);
						if($rs[$item[0]]<0)$rs[$item[0]]=0;
						break;
				default:
					throw new PException('{$item[0]}:第二参数错误', 999);
			}
			
			//如果有默认值，并且变量没有定义，那么才采用默认值
			//？？这个机制不是很好。变量一般情况不是存在不存在的问题，而是数据正确与否的问题
			//而且不能很好处理多种验证类型
		
		}
		
		return $rs;
	}
}







/**
 * 框架根据不同的服务对象，将插件分成多个不同的服务角色。
 * ApiBase是基于Base的基础上进行角色分裂。他的责任在于提供一个插件间内部的数据交互。
 * 
 * @example $handle = n_api('pevent');
 * 
 */
class ApiBase extends Base{
	
	public $pluginsConf__ = null;
	/**
	 * 提供一个统一的获取配置文件的方法
	 * 很多插件需要通过配置来实现不同 业务，所以提供该方法
	 * 
	 * linruiyan@2014-2-8 创建
	 * 
	 */
	public function getConf($key=''){
		//linruiyan@2014-2-8 名字变态一点，避免 冲突
		if(is_null($this->pluginsConf__)){
			$classname = strtolower(substr(get_class($this),0,-3));
			$this->pluginsConf__ = n_confGet($classname,"Y");
		}

		return ''===$key?$this->pluginsConf__:$this->pluginsConf__['data'][$key];
	}
		
	/**
	 * 在插件被安装之前需要去检查操作系统，php，插件等是否能够提供运行环境。
	 * 这个方法写在这里是因为大部分插件没有涉及到特殊的类库等环境需求，那么在您 的插件代码中完全可以忽略他的存在。而且还不会因为插件的残缺导致安装流程无法进行。
	 * 
	 * 关系解释:
	 * 		d插件 A 依赖(depends)插件 B：要运行A必须安装 B。在有些情况下，A 不仅依赖 B，还要求 B 的特定版本。版本依赖通常有最低版本限制，A 更依赖于B的最新版而非某个特定版本。
	 * 		r--插件 A 推荐(recommends)插件 B：插件维护者认为所有用户都不会喜欢缺少某些功能的 A，而这些功能需要 B 来提供。
	 * 		s插件 A 建议(suggests)插件包 B：B 中某些功能与 A 的功能相关，并能够增强 A 的功能。这种关系通过声明插件 B 增强 Enhances 插件 A 来表示。
	 * 		c插件 A 与插件 B 冲突(conflicts)：如果系统中安装了 B 那么 A 无法运行。“Conflicts”常和“replaces”同时出现。
	 * 		r--插件 A 替换(replaces)插件包 B：B 安装的文件被 A 中的文件移除和覆盖了。
	 * 		p插件 A 提供(provides)插件包 B：A 中包含了 B 中的所有文件和功能。
	 * 
	 * 
	 * A[drscrp]B
	 * A[<=>]B
	 * A[d>2.0]B,A[d<2.0]B,A[d=2.0]B
	 * A[r]B
	 * A[s]B
	 * A[c]B
	 * A[r]B
	 * A[P]B
	 * 
	 * @example n_api('pevent')->installCheck();
	 * 
	 */
	public function installCheck($php='',$ext='',$os='',$nexus=''){

		//php版本
		//php扩展
		//操作系统
		if(empty($php)){
			$rs['php'] = n_state(TRUE,'PHP:未定义PHP版本需求');
		}else{
			$phpV = substr(phpversion(),0,strpos(phpversion(), '-'));
			if(strnatcmp($phpV, $phpLV)>=0){
				$rs['php'] = n_state(FALSE,"版本低于{$phpLV}");
			}
 
		}
		
		if(empty($ext)){
			$rs['ext'] = n_state(TRUE,'未定义:PHP扩展需求');
		}else{
			$extArr = explode(',',$ext);
			foreach($extArr as $key=>$val){
				$val = strtolower($val);
				$extTips[] = function_exists($val)?'':'';
				
			}
		}


		if(!in_array(N_OS,array('win','linux'))){
			$rs['os'] = "OS:未知系统需求";
		}else{
			if(N_OS!=$os){
				$rs['os'] = "OS:需要{$os}";
			}else{
				$rs['os'] = "OS:符合需求";
			}
		}
		
		if(empty($nexus)){
			$rs['plugins'] = "关系插件:未定义需求";
		}else{
			$nexusArr = explode(",",$nexus);
			foreach($nexusArr as $key=>$val){
				
				preg_match("/(.*)\\\[([dscp]+)([<=>]*)(.*)\\\](.*)/",$val,$match);
				
				switch($match[1]){
					case 'd':
						$rs['plugins'][] = "插件需求:依赖插件{$match['4']}";
						break;
					case 's':
						$rs['plugins'][] = "插件需求:建议插件{$match['4']}";
						break;
					case 'c':
						$rs['plugins'][] = "插件需求:冲突插件{$match['4']}";
						break;
					case 'p':
						$rs['plugins'][] = "插件需求:囊括插件{$match['4']}";
						break;
					default:
						$rs['plugins'][] = "插件需求:配置错误";
						break;
				}
			}	
		}
		
		return $rs;
	
	}
	/**
	 * 插件安装流程的一个环节，用户安装步骤的提示。安装者可以全面了解到插件的安装步骤，如果安装过程中出现异常可以得到提示，进行针对性的处理
	 * 如果您的代码没有特殊的安装业务。那么直接忽略即可
	 * 
	 * @example n_api('installSetup')->installSetup();
	 * 
	 */
	public function installSetup(){
		$rs['code'] = '代码';
		$rs['src'] = '原始数据';
		$rs['init'] = '初始化';
		$rs['callback'] = '回调';
		return $rs;
	}
	
	/**
	 * 插件如果在卸载时需要做某些特殊的处理。比如清空log，情空缓存，记录业务信息等。那么可以通过重写这个方法来实现卸载的特殊业务处理。
	 * 如果您的插件卸载没有特殊业务，那么请忽略
	 * 
	 * @example n_api('pevent')->installRemove();
	 * 
	 */
	public function installRemove(){
		$rs['code'] = '代码';
		$rs['src'] = '原始数据';
		$rs['conf'] = '配置文件';
		return $rs;		
	}
	
	/**
	 * 插件如果在更新时需要做某些特殊的处理。比如停止服务，清空log，情空缓存，记录业务信息等。那么可以通过重写这个方法来实现更新的特殊业务处理。
	 * 如果您的插件更新没有特殊业务，那么请忽略
	 * 
	 * @example n_api('pevent')->installUpdate();
	 * 
	 */
	public function installUpdate(){
		$rs['code'] = '代码';
		$rs['src'] = '原始数据';
		$rs['init'] = '初始化';
		$rs['callback'] = '回调';
		return $rs;
		
	}
}




/**
 * FaceBase定义了一个能直接提供外部访问的机制。我们通过浏览器能够直接访问到。
 * 结合Api，我们可以给与提供不同的服务级别。
 * Face我们认为是安全的，因为其他插件将无法直接访问Face，用户能通过浏览器访问Face，来完成对数据的操作。
 * Api相对来说要比较开放，所有的插件都可以直接访问。
 * 所以在某些情况，我们将不希望其他插件来操作的数据放在Face中实现，将希望其他插件对数据进行操作的放在Api中
 *
 * 另外Face是一定需要有html输出的，不然页面展示是混乱的。
 * 
 * 
 */
class FaceBase extends Base{
	
	/**
	 * 提供一个快捷的获取自己的tpl文件，该方法能智能识别出所调用的插件的目录，并自动包含插件下对应的html文件
	 * 
	 * @example $this->_html('index',$data);
	 * 			以上代码将包含html/index.html的代码，并且在index.html中可以使用$data中的数据
	 * 
	 * @param 	string 		加载的文件名，tpl文件。所有的tpl都存放于html目录中
	 * @param 	array 		需要在tpl中使用的数据。
	 */	
	protected function _html($filename,$data = array()) {

		$trace = debug_backtrace();
		preg_match("/.*\\/(\\w*)\\/.*\\.php$/",$trace[0]['file'],$rs);
	
		$event = $rs[1];
		$path = N_PLUGINS . '/' . "{$event}/html/" . $filename . '.html';
		
		
		if(!file_exists($path)){
			throw new PException('TPL:找不到模板文件'.$path , 1);
		}
		include $path;
	}
	
    /**
     * 向前端输出JSON数据
     * @param type $data        数据主题
     * @param type $ret         返回码，0 为成功 非0为错误码
     * @param type $msg         错误信息，出错时的信息
     */
	protected function _json($data, $ret = 0, $msg = "") {
		$jsonp_func_name = $_GET['jsonpcallback'];
		$return = array(
			'ret' => $ret,
			'msg' => $msg,
			'data' => $data
		);
		echo "$jsonp_func_name(" . json_encode($return) . ");";
	}

	
	/**
	 * 这个函数对插件内部的保护和友善提示。在您访问一个不存在的方法时。将会自动调用这个函数，这个函数将给你一个准确的提示。
	 * 如果您开启了错误log，那么您能很快的通过日志得到一些隐蔽的bug
	 * 
	 * @param string 	$fun 		方法名
	 * @param array 	$param 		函数的参数，这里将被忽略
	 * 
	 */
	public function __call($fun,$param){
		$filepath = N_PLUGINS . '/' . N_EVENT . '/face/'.N_ACTION.'.php';
		// echo $filepath;exit;
		if(file_exists($filepath)){
			include $filepath;
		}else{
			throw new Exception("找不到调用方法{$fun}");
		}
	}
	

	/**
	 * 获取配置文件的数据
	 * 每个插件都有自己的提供一个配置文件。配置名和插件名一样。
	 * Face作为插件的访问入口，提供一个默认的配置文件修改方法。
	 * 
	 * @example http://xxx.domain.com/pevent/confEdit.html
	 * 
	 */
	public function confEdit(){
		$conf = n_confGet(N_EVENT);
		$url = n_url(N_EVENT,'confSave');
		$jsonConf = json_encode($conf['data']);
		echo <<<EOF
<div>
	<form method="post" action="{$url}">
	<textarea name="confVal">{$jsonConf}</textarea>
	<input type="submit" />
	</form>
</div>
EOF;
//		$this->_html('editConf',$conf);

	}
	/**
	 * 保存配置文件的数据
	 * 每个插件都有自己的提供一个配置文件。配置名和插件名一样。
	 * Face作为插件的访问入口，提供一个默认的配置文件修改方法。
	 * 
	 * @example http://xxx.domain.com/pevent/confSave.html
	 * 
	 */
	public function confSave(){
		$param = $this->param("confVal:char");
		$rs = json_decode(stripslashes($param['confVal']),true);
		if(empty($rs)){
			throw new PException("配置数据格式不正确，请正确输入JSON");
		}
		n_confSave(N_EVENT, $rs);
		echo <<<EOF
<div class="n-alert">保存成功！</div>

EOF;
	}
	
	public function msg(){
		$param = $this->param("msg:char");
		echo <<<EOF
<div class="content">
	<div class="n-alert">{$param['msg']}</div>
</div>		
EOF;
	}

	
}


/**
 * FaceBase定义了一个能直接提供外部访问的机制。我们通过浏览器能够直接访问到。
 * 结合Api，我们可以给与提供不同的服务级别。
 * Face我们认为是安全的，因为其他插件将无法直接访问Face，用户能通过浏览器访问Face，来完成对数据的操作。
 * Api相对来说要比较开放，所有的插件都可以直接访问。
 * 所以在某些情况，我们将不希望其他插件来操作的数据放在Face中实现，将希望其他插件对数据进行操作的放在Api中
 *
 * 另外Face是一定需要有html输出的，不然页面展示是混乱的。
 * 
 * 
 */
class AdminBase extends Base{
	
	/**
	 * 提供一个快捷的获取自己的tpl文件，该方法能智能识别出所调用的插件的目录，并自动包含插件下对应的html文件
	 * 
	 * @example $this->_html('index',$data);
	 * 			以上代码将包含html/index.html的代码，并且在index.html中可以使用$data中的数据
	 * 
	 * @param 	string 		加载的文件名，tpl文件。所有的tpl都存放于html目录中
	 * @param 	array 		需要在tpl中使用的数据。
	 */	
	protected function _html($filename,$data = array()) {

		$trace = debug_backtrace();
		preg_match("/.*\\/(\\w*)\\/.*\\.php$/",$trace[0]['file'],$rs);
	
		$event = $rs[1];
		$path = N_PLUGINS . '/' . "{$event}/html/" . $filename . '.html';
		
		
		if(!file_exists($path)){
			throw new PException('TPL:找不到模板文件'.$path , 1);
		}
		include $path;
	}
	
    /**
     * 向前端输出JSON数据
     * @param type $data        数据主题
     * @param type $ret         返回码，0 为成功 非0为错误码
     * @param type $msg         错误信息，出错时的信息
     */
	protected function _json($data, $ret = 0, $msg = "") {
		$jsonp_func_name = $_GET['jsonpcallback'];
		$return = array(
			'ret' => $ret,
			'msg' => $msg,
			'data' => $data
		);
		echo "$jsonp_func_name(" . json_encode($return) . ");";
	}

	
	/**
	 * 这个函数对插件内部的保护和友善提示。在您访问一个不存在的方法时。将会自动调用这个函数，这个函数将给你一个准确的提示。
	 * 如果您开启了错误log，那么您能很快的通过日志得到一些隐蔽的bug
	 * 
	 * @param string 	$fun 		方法名
	 * @param array 	$param 		函数的参数，这里将被忽略
	 * 
	 */
	public function __call($fun,$param){
		$filepath = N_PLUGINS . '/' . N_EVENT . '/face/'.N_ACTION.'.php';
		// echo $filepath;exit;
		if(file_exists($filepath)){
			include $filepath;
		}else{
			throw new Exception("找不到调用方法{$fun}");
		}
	}
	

	/**
	 * 获取配置文件的数据
	 * 每个插件都有自己的提供一个配置文件。配置名和插件名一样。
	 * Face作为插件的访问入口，提供一个默认的配置文件修改方法。
	 * 
	 * @example http://xxx.domain.com/pevent/confEdit.html
	 * 
	 */
	public function confEdit(){
		$conf = n_confGet(N_EVENT);
		$url = n_url(N_EVENT,'confSave');
		$jsonConf = json_encode($conf['data']);
		echo <<<EOF
<div>
	<form method="post" action="{$url}">
	<textarea name="confVal">{$jsonConf}</textarea>
	<input type="submit" />
	</form>
</div>
EOF;
//		$this->_html('editConf',$conf);

	}
	/**
	 * 保存配置文件的数据
	 * 每个插件都有自己的提供一个配置文件。配置名和插件名一样。
	 * Face作为插件的访问入口，提供一个默认的配置文件修改方法。
	 * 
	 * @example http://xxx.domain.com/pevent/confSave.html
	 * 
	 */
	public function confSave(){
		$param = $this->param("confVal:char");
		$rs = json_decode(stripslashes($param['confVal']),true);
		if(empty($rs)){
			throw new PException("配置数据格式不正确，请正确输入JSON");
		}
		n_confSave(N_EVENT, $rs);
		echo <<<EOF
<div class="n-alert">保存成功！</div>

EOF;
	}
	
	public function msg(){
		$param = $this->param("msg:char");
		echo <<<EOF
<div class="content">
	<div class="n-alert">{$param['msg']}</div>
</div>		
EOF;
	}

	
}



/**
 * 在某些时候我们需要把其他插件的某一部分直接嵌套到另外的一个插件页面中
 * 
 * 和Api相比，除了数据还需要将样式也展示出来。
 * 在一些html代码中我们可以使用调取其他插件中的模块
 * 
 * @example $handle = n_mod('pevnet');
 * 
 */
class ModBase extends Base{
	
	/**
	 * 提供一个快捷的获取自己的tpl文件，该方法能智能识别出所调用的插件的目录，并自动包含插件下对应的html文件
	 * 
	 * @example $this->_html('index',$data);
	 * 			以上代码将包含html/index.html的代码，并且在index.html中可以使用$data中的数据
	 * 
	 * @param 	string 		加载的文件名，tpl文件。所有的tpl都存放于html目录中
	 * @param 	array 		需要在tpl中使用的数据。
	 */	
	protected function _html($filename,$data = array()) {

		$trace = debug_backtrace();
		preg_match("/.*\\/(\\w*)\\/.*\\.php$/",$trace[0]['file'],$rs);
	
		$event = $rs[1];
		$path = N_PLUGINS . '/' . "{$event}/html/" . $filename . '.html';
		
		
		if(!file_exists($path)){
			throw new PException('TPL:找不到模板文件' . $this->name , 1);
		}
		include $path;
	}	
}



/**
 * Open是一个webservice入口，为其他系统输出某些数据。
 * 和Api相比，Open输出数据了
 * 和Face相比，Open输出的是json或者xml
 * 
 * @example n_open('pevent');
 */
class OpenBase extends Base{}


class RpcBase extends Base{}






/**
 * Script是用于后台CLI执行的脚本。
 * 用户系统定义执行的任务。
 * 
 * @example /bin/php /www/index.php pevent data
 * 
 */
class ScriptBase extends Base {
	public function __call($name, $arguments) {

		daemon(N_EVENT, $_SERVER['argv'][3]);
	}
}









/**
 * 当一个插件相对比较复杂时，可以把一些相同业务的功能放在一起，形成一个业务类。
 * 在插件内部如果需要调用，那么直接new classname()即可
 * 
 * @example $handle = new classname();
 * 
 */
class LibBase extends Base{}

/**
 * PHP异步队列脚本
 *
 * 执行：/opt/bin/php /opt/webapps/m3/index.php demo daemon demo
 * 
 */
abstract class DaemonBase extends Base {

	/**
	 * $mqkey 队列的key
	 * 
	 * @var array
	 */
	protected $mqkey;

	/**
	 * $serverAlias
	 *
	 * 队列服务 配置的别名
	 * 
	 * @var string
	 */
	protected $serverAlias = 'c001';

	/**
	 * $processNum 进程的最大数
	 * 
	 * @var integer
	 */
	protected $processNum = 1;

	/**
	 * $batchNum 消费者(程序执行) 批量处理的 数量
	 * @var integer
	 */
	protected $batchNum = 1;



	public function __construct() {

		$this->initialize();

		$serverAlias = $this->serverAlias;
		
		$this->redis = n_api('redis')->$serverAlias;
		$this->log = n_api('nlog');

		$this->daemon = $_SERVER['argv'][3];
		$this->check();

		$data = array();
		$start_time = time();
		while(true) {

			$row = $this->redis->lPop($this->mqkey);

			if(!empty($row)) {
				$this->recordData($row);

				$data[] = json_decode($row, true);
				unset($row);
				
			} else {
				usleep(500000);
			}

			$run_time = time();

			//超过批量执行的数量或 上次执行时间到现在超过2分钟
			if(count($data) >= $this->batchNum || (($run_time - $start_time) > 10 && !empty($data)) ) {
				try {
					$data = $this->batchNum == 1 ? $data[0] : $data;

					$this->checkDBConnect();

					$bRet = $this->run($data);
					if(true === $bRet) {
						$this->recordSuccLog($data);
					} else {
						$this->recordFailureLog($data);
					}
					
				} catch(Exception $e) {
					$this->recordFailureLog($data, $e->getMessage());
				}
				
				$start_time = time();
				unset($data);
				usleep(10000);
			}
			
		}
	}

	/**
	 * setMQKey
	 *
	 * 设置队列的key
	 * 
	 * @param string $mqkey 队列的key
	 */
	public function setMQKey($mqkey) {
		if(!empty($this->mqkey)) {
			throw new Exception('队列的key已经设置', 2001);
		}
		$this->mqkey = $mqkey;
	}


	/**
	 * check 
	 *
	 * 检测初始化参数
	 * 
	 * @return void
	 */
	protected function check() {
		try {
			if(!$this->redis) {
				throw new Exception('redis 连接失败!', 1001);
			}
			if(empty($this->mqkey)) {
				throw new Exception('队列的key还没有设置', 1002);
			}

			$this->init_log();

			$this->checkProcessNum();
		} catch(Exception $e) {
			exit($e->getMessage());
		}
	}

	/**
	 * checkDBConnect
	 *
	 * 检查Mysql的连接是否正常，默认mysql 的wait_timeout参数为：28800.
	 * 
	 * @return boolean
	 */
	protected function checkDBConnect() {

		$oRedis = n_api('redis')->c001;

		$timeoutKey = 'mysql:timeout:' . $this->mqkey;
		$time = $oRedis->get($timeoutKey);
		$time += 0;

		$expire = $oRedis->get('mysql:wait:expire');
		$expire += 0;

		$expire = $expire > 0 && $expire <= 28800 ? $expire : 14400;

		//如果还在有效期内，不用检测DB连接
		if(time() - $time < $expire) {
			return true;
		} 

		$oMysql = n_api('mysql');

		$conf = n_confGet('mysql');
		$data = $conf['data'];
		// print_r($conf);
		if(!empty($data)) {
			foreach ($data as $key => $v) {
				$oMysql->reconnect($key);
			}
		}
		
		$db = $oMysql->db001;

		$sql = "SELECT * FROM mysql_main";		
		$data = $db->select($sql);
		if(!empty($data)) {
			foreach ($data as $k => $v) {
				$cname = $v['cname'];
				$oMysql->reconnect($cname);
			}
		}


		$oRedis->set($timeoutKey, time());

		return true;
	}


	/**
	 * checkProcessNum
	 *
	 * 检测进程数
	 * 
	 * @return void
	 */
	protected function checkProcessNum() {
		$fp = popen("/bin/ps -ef | grep " . N_EVENT . " | grep 'daemon' | grep '{$this->daemon}'| grep -v 'grep' | grep -v vim | wc -l", 'r');

		if(!$fp) {
			throw new Exception('检测进程的shell 打开失败', 3001);
		}

		$num = fgets($fp, 1024);

		pclose($fp);

		$num += 0;

		if($num > $this->processNum) {
			throw new Exception('当前进程数大于设置的进程数,当前进程数:' . $num . ". 设置的最大进程数为:" . $this->processNum, 3002);
			
		}
	}

	/**
	 * init_log
	 *
	 * 获取日志目录，如果没有创建日志目录
	 * 
	 * @return void
	 */
	protected function init_log() {
		/*
		$nconfig = n_api('nconfig')->get('monitor_log_dir');
		$log_dir = $nconfig['monitor_log_dir'];
		*/
		$log_dir = '/DATA/logs/monitor_logs/';

		$daemon = $this->daemon;

		$daemon = str_replace(array(':'), array('_'), $daemon);

		$this->logPath = $log_dir . '/' . N_EVENT . '/queue/' . $daemon . '/';

		$oCommon = n_api('common');

		$this->dataPath = $this->logPath . 'data';
		$this->failurePath = $this->logPath . 'failure';
		$this->succPath = $this->logPath . 'succ';

		try {
			$oCommon->mkdir($this->dataPath);
			$oCommon->mkdir($this->failurePath);
			$oCommon->mkdir($this->succPath);
		} catch(Exception $e) {
			throw new Exception($e->getMessage(), 4001);
		}
	}

	/**
	 * recordData
	 *
	 * 记录队列的所有数据
	 * 
	 * @param  string $data 
	 * @return void
	 */
	/*protected function recordData($data) {

		if(empty($data)) return false;
		$logFile = $this->dataPath . '/' . date('Ymd') . '.log';

		$this->log->addOrigin2File($logFile, $data);
		
	}*/
	/**
	 * recordSuccLog
	 *
	 * 记录成功的数据
	 * 
	 * @param  array $data 
	 * @return void
	 */
	/*public function recordSuccLog($data) {
		if(empty($data) || !is_array($data)) return false;

		$logFile = $this->succPath . '/' . date('Ymd') . '.log'; 


		$data = json_encode($data) . '||' . date('Y-m-d H:i:s');
		$this->log->addOrigin2File($logFile, $data);
	}*/

	/**
	 * recordFailureLog
	 *
	 * 记录失败的数据
	 * 
	 * @param  array $data     
	 * @param  string $errorMsg 错误原因
	 * @return void
	 */
	/*public function recordFailureLog($data, $errorMsg = '') {
		if(empty($data) || !is_array($data)) return false;

		$logFile = $this->failurePath . '/' . date('Ymd') . '.log'; 
		$data['errorMsg'] = $errorMsg;

		$data = json_encode($data) . '||' . date('Y-m-d H:i:s');
		$this->log->addOrigin2File($logFile, $data);
	}*/

	/**
	 * initialize
	 *
	 * 队列初始化操作
	 * 
	 * @return void
	 */
	abstract public function initialize();

	/**
	 * run
	 *
	 * worker执行具体的业务逻辑
	 * 
	 * @param  array $data 从队列中取出的数据
	 * @return boolean true:成功|false:失败. 失败抛出异常也可以
	 */
	abstract public function run($data);

}

interface ScrapyInterface
{
	/**
     * 提供爬取端访问的间隔时间
     * @return void
     */
	public function generateTaskSleepTime();

	/**
     * 提供爬取端访问的cookies
     * @return void
     */
	public function generateTaskCookies();

	/**
     * 提供爬取端访问agent
     * @return void
     */
	public function generateTaskAgents();

	/**
     * 提供爬取端访问Header
     * @return void
     */
	public function generateTaskHeaders();
}